﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DimensionsHelper.Common.Contracts.Table;
using DimensionsHelper.Common.Table;
using DimensionsHelper.Common.Utils;

namespace DimensionsHelper.Services.CsvReader;

public class CsvStreamReader : ITableReader, IDisposable
{
    private readonly List<CsvLine> _lines = [];

    private readonly Dictionary<string, List<int>> _tableNameToIndex = new();
    private readonly List<CsvTable> _tables = [];

    private bool _atStart = true;
    private CsvLine? _currentLine;

    private int _currentLineIndex;

    private char _delimiter = ',';
    private bool _disposed;
    private CsvLine? _fileSpecLine;

    private int _lastTableEndIndex;

    private ReadCellDataHandler<string>? _readCellDataHandler;
    private StreamReader? _streamReader;
    private EventHandler<TableFinishedEventArgs>? _tableFinishedHandler;

    private int _version;

    public void Dispose()
    {
        if (!_disposed)
        {
            _streamReader?.Dispose();
            _streamReader = null;
        }

        Clear();

        GC.SuppressFinalize(this);
        _disposed = true;
    }

    public event ReadCellDataHandler<string> ReadCellData
    {
        add
        {
            if (_readCellDataHandler != null)
            {
                foreach (var cb in _readCellDataHandler.GetInvocationList())
                {
                    _readCellDataHandler -= (ReadCellDataHandler<string>)cb;
                }
            }

            _readCellDataHandler += value;
        }

        remove => _readCellDataHandler -= value;
    }

    public event EventHandler<TableFinishedEventArgs> TableFinished
    {
        add => _tableFinishedHandler += value;
        remove => _tableFinishedHandler -= value;
    }

    public int LinesCount => _lines.Count;

    public int TablesCount { get; private set; }

    public ITable? FirstTable { get; private set; }

    public ITable? LastTable { get; private set; }

    public ITable? FirstOriginalTable => _tables.Count > 0 ? _tables[0] : null;

    public ITable? LastOriginalTable => _tables.Count > 0 ? _tables[^1] : null;

    public ILine? FileSpecLine => _fileSpecLine;

    public void SetDelimiter(char delimiterChar)
    {
        _delimiter = delimiterChar;
    }

    public bool AutoDetectDelimiter { get; set; }

    public void ReadAllLines()
    {
        if (_streamReader is null)
        {
            throw new Exception("CsvStreamReader：尚未指定任何输入数据源。");
        }

        if (AutoDetectDelimiter)
        {
            DetectDelimiter();
        }

        if (_streamReader.BaseStream.Position > 0)
        {
            _streamReader.BaseStream.Seek(0, SeekOrigin.Begin);
            _streamReader.DiscardBufferedData();
        }

        _currentLineIndex = 0;
        _atStart = true;

        while (ReadLine())
        {
            _currentLineIndex++;
        }
    }

    public void ReadStream(StreamReader reader)
    {
        var currentReader = _streamReader;
        _streamReader = reader;
        ReadAllLines();
        _streamReader = currentReader;
    }

    public void ReadStream(FileStream fileStream)
    {
        using var reader = new StreamReader(fileStream, FileHelper.DetectFileEncoding(fileStream));
        ReadStream(reader);
    }

    public void BuildTables(ITableRecognizer recognizer)
    {
        if (_lines.Count == 0)
        {
            return;
        }

        _tableNameToIndex.Clear();
        _tables.Clear();
        TablesCount = 0;
        _fileSpecLine = null;

        var index = 0;

        while (index < _lines.Count)
        {
            var line = _lines[index];

            if (index < 3 && line.NotEmptyCellCount == 1 && !line[0].IsEmpty && _fileSpecLine == null)
            {
                _fileSpecLine = line;
            }

            if (line.Count >= 1 && !line[0].IsEmpty && recognizer.IsTableTitleCell(line[0]))
            {
                var table = new CsvTable(line[0],
                    _tables.Count > 0 ? _tables[^1] : null,
                    _lastTableEndIndex);
                FillHeader(table, recognizer);
                FillBody(ref index, table, recognizer);
                table.Finish();
                _tableFinishedHandler?.Invoke(this, new TableFinishedEventArgs(table, _tables.Count));
                _tableNameToIndex.AddOrUpdate(table.TitleCell.Text, () => [], 
                    list => list.Add(_tables.Count));
                _tables.Add(table);
                _lastTableEndIndex = index;
                _version++;
            }

            index++;
        }

        if (_tables.Count > 0)
        {
            FirstTable = FirstOriginalTable;
            LastTable = LastOriginalTable;
        }

        TablesCount = _tables.Count;
    }

    public void InsertTablesAt(ITableReader reader, int rowIndex)
    {
        if (FirstTable == null || LastTable == null)
        {
            CopyTablesFromReader(reader);
            return;
        }

        var findTable = FindOriginalTableByRowIndex(rowIndex) as CsvTable;
        InsertTablesAt(reader, findTable);
    }

    public void InsertTablesAt(ITableRecognizer recognizer, StreamReader stream, int rowIndex)
    {
        using var reader = new CsvStreamReader();
        reader.AutoDetectDelimiter = AutoDetectDelimiter;
        reader.ReadStream(stream);
        reader.BuildTables(recognizer);
        InsertTablesAt(reader, rowIndex);
    }

    public void InsertTablesAt(ITableRecognizer recognizer, FileStream fileStream, int rowIndex)
    {
        using var stream = new StreamReader(fileStream, FileHelper.DetectFileEncoding(fileStream));
        InsertTablesAt(recognizer, stream, rowIndex);
    }

    public void InsertTablesBefore(ITableReader reader, string tableName)
    {
        if (reader.FirstTable == null || reader.LastTable == null)
        {
            return;
        }

        if (FirstTable == null || LastTable == null)
        {
            CopyTablesFromReader(reader);
            return;
        }

        if (_tableNameToIndex.TryGetValue(tableName, out var tableIndex) && tableIndex.Count > 0)
        {
            if (tableIndex.Count != 1)
            {
                
            }

            InsertTablesAt(reader, _tables[tableIndex[0]]);
        }
        else
        {
            var readerFirst = (CsvTable)reader.FirstTable;
            ((CsvTable)LastTable).NextTable = readerFirst;
            readerFirst.PreviousTable = LastTable;
            TablesCount += reader.TablesCount;
        }
    }

    public void InsertTablesBefore(ITableRecognizer recognizer, StreamReader stream, string tableName)
    {
        using var reader = new CsvStreamReader();
        reader.AutoDetectDelimiter = AutoDetectDelimiter;
        reader.ReadStream(stream);
        reader.BuildTables(recognizer);
        InsertTablesBefore(reader, tableName);
    }

    public void InsertTablesBefore(ITableRecognizer recognizer, FileStream fileStream, string tableName)
    {
        using var stream = new StreamReader(fileStream, FileHelper.DetectFileEncoding(fileStream));
        InsertTablesBefore(recognizer, stream, tableName);
    }


    public void ReplaceTables(IEnumerable<ITable> tables)
    {
        // 没有表格可以被替换
        if (_tables.Count == 0)
        {
            return;
        }

        foreach (var table in tables.Cast<CsvTable>())
        {
            var index = _tables.FindIndex(t => t.TitleCell.Text.Equals(table.TitleCell.Text));
            if (index >= 0)
            {
                _tables[index] = table;
            }
        }
    }
    

    public void FinishStream()
    {
        _streamReader = null;
    }


    public ITable? FindTable(string title)
    {
        if (_tableNameToIndex.TryGetValue(title, out var tableIndices) && tableIndices.Count > 0)
        {
            return _tables[tableIndices[0]];
        }

        return null;
    }


    public ITable[] FindTables(string title)
    {
        if (_tableNameToIndex.TryGetValue(title, out var tableIndices) && tableIndices.Count > 0)
        {
            return tableIndices.Select(i => _tables[i]).ToArray<ITable>();
        }

        return [];
    }


    public void Clear()
    {
        _atStart = true;
        _currentLineIndex = 0;
        _currentLine = null;
        _fileSpecLine = null;
        _lastTableEndIndex = 0;

        _lines.Clear();
        _tables.Clear();
        _tableNameToIndex.Clear();

        TablesCount = 0;
        FirstTable = null;
        LastTable = null;
    }

    public IEnumerator<ITable> GetEnumerator()
    {
        return new Enumerator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    private void DetectDelimiter()
    {
        var contents = new List<string>();

        for (var i = 0; i < 15; i++)
        {
            var line = _streamReader!.ReadLine();

            if (line != null)
            {
                contents.Add(line);
            }
            else
            {
                break;
            }
        }

        _streamReader!.BaseStream.Seek(0, SeekOrigin.Begin);
        _streamReader.DiscardBufferedData();

        _delimiter = CsvHelper.GetDelimiter(contents);
    }

    private bool ReadLine()
    {
        if (_streamReader?.EndOfStream is true)
        {
            return false;
        }

        var line = _streamReader?.ReadLine();

        if (line is null)
        {
            return false;
        }

        if (_atStart)
        {
            if (line.Length > 0 && line[0] == 65279)
            {
                line = line[1..];
            }

            _atStart = false;
        }

        var csvLine = new CsvLine(_currentLineIndex, _currentLine);

        if (_readCellDataHandler is null)
        {
            CsvHelper.ReadLine(line, _delimiter, _currentLineIndex, str => csvLine.Add(str));
        }
        else
        {
            CsvHelper.ReadLine(line, _delimiter, _currentLineIndex,
                str => _readCellDataHandler.Invoke(this, new ReadCellDataEventArgs<string>(str, csvLine)));
        }

        _lines.Add(csvLine);
        _currentLine = csvLine;

        return true;
    }

    private void FillHeader(CsvTable table, ITableRecognizer recognizer)
    {
        var index = table.TitleCell.Line.OriginalIndex - 1;

        while (index > _lastTableEndIndex)
        {
            var line = _lines[index];

            if (recognizer.IsTableStartLine(line))
            {
                break;
            }

            table.Add(line);
            index--;
        }
    }

    private void FillBody(ref int index, CsvTable table, ITableRecognizer recognizer)
    {
        while (index < _lines.Count)
        {
            var line = _lines[index];
            table.Add(line);

            if (recognizer.IsTableBaseLine(line))
            {
                line.LineType |= LineType.Base;
            }

            if (recognizer.IsTableEndLine(line))
            {
                break;
            }

            index++;
        }
    }

    // 向当前已读取表格列表中插入表格时，为了以后插入表格时方便，不修改原始表格列表。
    // 只修改 ITable.NextTable 和 ITable.LastTable，以修改链表的形式添加新的表格。

    private void CopyTablesFromReader(ITableReader reader)
    {
        FirstTable = reader.FirstTable;
        LastTable = reader.LastTable;
        TablesCount = reader.TablesCount;
    }

    private void InsertTablesAt(ITableReader reader, CsvTable? table)
    {
        // 如果 reader 已读取的表格是空白，直接返回
        if (reader.FirstTable == null || reader.LastTable == null)
        {
            return;
        }

        // 如果目标表格是空白，插入到最后
        if (table == null)
        {
            // 如果表格是空白，整体替换
            if (LastTable == null)
            {
                FirstTable = reader.FirstTable;
                LastTable = reader.LastTable;
            }
            // 链接到表格链表末尾
            else
            {
                ((CsvTable)LastTable).NextTable = reader.FirstTable;
                ((CsvTable)reader.FirstTable).PreviousTable = LastTable;
            }

            TablesCount += reader.TablesCount;

            return;
        }

        // 如果目前没有读取到任何表格，将插入的 ITextReader 已读取到的表格整体插入
        if (FirstTable == null || LastTable == null)
        {
            CopyTablesFromReader(reader);
            return;
        }

        // 如果插入到开头，更新 FirstTable
        if (table == FirstOriginalTable)
        {
            // 如果已经在前方插入过表格，链接到已插入表格后面。
            if (table.PreviousTable != null)
            {
                ((CsvTable)table.PreviousTable).NextTable = reader.FirstTable;
                ((CsvTable)reader.FirstTable).PreviousTable = table.PreviousTable;
            }
            else
            {
                FirstTable = reader.FirstTable;
            }
        }
        // 如果插入到末尾，更新 LastTable，检查是否已经插入过表格。
        else if (table == LastOriginalTable)
        {
            if (table.NextTable != null)
            {
                ((CsvTable)LastTable).NextTable = reader.FirstTable;
                ((CsvTable)reader.FirstTable).PreviousTable = LastTable;
            }

            LastTable = reader.LastTable;
        }

        if (table.PreviousTable != null)
        {
            var prev = (CsvTable)table.PreviousTable;
            var readerFirst = (CsvTable)reader.FirstTable;
            prev.NextTable = readerFirst;
            readerFirst.PreviousTable = prev;
        }

        table.PreviousTable = reader.LastTable;
        ((CsvTable)reader.LastTable).NextTable = table;

        TablesCount += reader.TablesCount;
    }


    private ITable? FindOriginalTableByRowIndex(int rowIndex)
    {
        if (rowIndex < 0)
        {
            return null;
        }

        if (rowIndex <= _tables[0].EndLineIndex)
        {
            return _tables[0];
        }

        if (rowIndex >= _tables[^1].StartLineIndex)
        {
            return _tables[^1];
        }

        // 二分查找

        var high = _tables.Count - 1;
        var low = 0;

        while (low <= high)
        {
            var mid = low + ((high - low) / 2);
            var t = _tables[mid];

            if (t.StartLineIndex <= rowIndex && t.EndLineIndex >= rowIndex)
            {
                return t;
            }
            // 如果查找位置在 mid 索引表格结束行索引的后面，将查找区间修改成右侧区间

            if (t.EndLineIndex < rowIndex)
            {
                // 如果下一个原始表格末尾行大于等于查找行，返回下一个原始表
                if (t.NextOriginalTable != null && t.NextOriginalTable.EndLineIndex > rowIndex)
                {
                    return t.NextOriginalTable;
                }

                low = mid + 1;
            }
            // 此时，t.StartLineIndex > rowIndex，将查找区间修改成左侧区间
            else
            {
                high = mid - 1;
            }
        }

        return null;
    }

    private class Enumerator(CsvStreamReader reader) : IEnumerator<ITable>
    {
        private readonly int _version = reader._version;

        private ITable? _current;

        public ITable Current => _current!;

        object IEnumerator.Current => Current;

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }

        public bool MoveNext()
        {
            CheckVersion();
            _current = _current == null ? reader.FirstTable : Current.NextTable;
            return _current != null;
        }

        public void Reset()
        {
            CheckVersion();
            _current = null;
        }

        private void CheckVersion()
        {
            if (_version != reader._version)
            {
                throw new InvalidOperationException();
            }
        }
    }
}